import { Reducer, useCallback, useReducer, useState } from "react";
import { useMountedRef } from "./useMountedRef";

interface State<D> {
  error: Error | null;
  data: D | null;
  stat: "idle" | "loading" | "error" | "success";
}

const defaultInitialState: State<null> = {
  error: null,
  data: null,
  stat: "idle",
};

const defaultInitialConfig = {
  throwOnError: false,
};

const asyncReducer = <D>(state: State<D>, args: Partial<State<D>>) => ({
  ...state,
  ...args,
});
export const useAsync = <D extends unknown>(
  initialState?: State<D>,
  initialConfig?: typeof defaultInitialConfig
) => {
  const mountedRef = useMountedRef();
  const [state, dispatch] = useReducer<Reducer<State<D>, Partial<State<D>>>>(
    asyncReducer,
    {
      ...defaultInitialState,
      ...initialState,
    }
  );
  const safeDispatch = useCallback(
    (args: Partial<State<D>>) => {
      if (!mountedRef.current) return;
      return dispatch(args);
    },
    [dispatch, mountedRef]
  );
  const [retry, setRetry] = useState(() => () => {});
  const config = { ...defaultInitialConfig, ...initialConfig };

  const setData = useCallback(
    (data: D) => {
      safeDispatch({
        error: null,
        data: data,
        stat: "success",
      });
    },
    [safeDispatch]
  );

  const setError = useCallback(
    (error: Error) => {
      safeDispatch({
        error: error,
        data: null,
        stat: "error",
      });
    },
    [safeDispatch]
  );

  const run = useCallback(
    (promise: Promise<D>, runConfig?: { retry: () => Promise<D> }) => {
      if (!promise || !promise.then) {
        throw new Error("请传入 Promise 类型数据");
      }
      safeDispatch({ stat: "loading" });
      setRetry(() => () => {
        if (runConfig?.retry) {
          run(runConfig.retry(), runConfig);
        }
      });
      return promise
        .then((data) => {
          setData(data);
          return data;
        })
        .catch((error) => {
          setError(error);
          if (config.throwOnError) {
            return Promise.reject(error);
          }
          return error;
        });
    },
    [config.throwOnError, setData, setError, safeDispatch]
  );

  return {
    isIdle: state.stat === "idle",
    isLoading: state.stat === "loading",
    isError: state.stat === "error",
    isSuccess: state.stat === "success",
    run,
    retry,
    setData,
    setError,
    ...state,
  };
};
